# -*- coding: utf-8 -*-

import logging
import traceback

from flask import current_app, jsonify, abort, request
from werkzeug.exceptions import BadRequest

from flask_restful import Resource, inputs, reqparse, fields, marshal_with

from mongoengine import errors as mongo_errors
import requests

from utils.auth import current_user
from ins import ins, ins_api, models


logger = logging.getLogger(__name__)

INS_UTILS_SEARCH_URL = 'http://simonq.ali/s/'
SUBSCRIPTION_TYPE = [
    'publisher',
    'hashtag',
    # 'place',
]
GRAPH_SORT_TYPE = [
    'created',
    'published',
    # 'place',
]


class Graph(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()

    @marshal_with(models.Marshal.graph, envelope='data')
    def get(self, graph_id=None):
        if graph_id is not None:
            logger.debug('Get graph %s', graph_id)
            graph = models.Graph.objects(pk=graph_id).first()
            if graph is None:
                logger.info('Specified graph not existed: %s', graph_id)
                abort(400, 'Specified graph not existed')
            logger.debug('Get graph ends. %s', graph_id)
            return graph

        self.parser.add_argument(
            'subscribed',
            type=inputs.boolean,
            default=False
        )
        self.parser.add_argument(
            'page',
            type=inputs.natural,
            default=0,
        )
        self.parser.add_argument(
            'sort_type',
            choices=GRAPH_SORT_TYPE,
            default='created',
        )
        logger.debug('Get graph list')

        args = self.parser.parse_args()
        subscribed_only = args['subscribed']
        page = args['page']
        sort_type = args['sort_type']
        page_len = current_app.config['GRAPH_PAGE_LENGTH']

        if subscribed_only:
            queryset = models.Graph.objects_subscribed
        else:
            queryset = models.Graph.objects

        if sort_type == 'published':
            graphs = queryset.order_by('-date')[page * page_len: (page + 1) * page_len]
        else:
            graphs = queryset.order_by('-created_ts')[page * page_len: (page + 1) * page_len]
        for g in graphs:
            if not g.publisher:
                try:
                    publisher = models.Publisher.objects.get(pk=g.publisher_id)
                except (mongo_errors.DoesNotExist, mongo_errors.MultipleObjectsReturned):
                    continue
                g.publisher = {
                    'id': publisher.pk,
                    'username': publisher.username,
                    'full_name': publisher.fullname
                }
                # cached full_name in graph will not be updated
                # todo: fix it
                g.save()
        logger.debug('Get graph list ends.')
        return list(graphs)


ins_api.add_resource(Graph, '/', '/g/<graph_id>', endpoint='graph')


publisher_list_fields = {
    'publisher': fields.Nested(models.Marshal.publisher),
    'media': fields.List(fields.Nested(models.Marshal.graph))
}


class Publisher(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()
        self.parser.add_argument(
            'id',
            dest='id_',
            required=True,
            help='Missing publisher id'
        )
        self.parser.add_argument(
            'page',
            type=inputs.natural,
            default=0,
        )

    @marshal_with(publisher_list_fields, envelope="data")
    def get(self):
        self.parser.add_argument(
            'sort_type',
            choices=GRAPH_SORT_TYPE,
            default='created',
        )
        logger.debug('Get publisher list')
        args = self.parser.parse_args()
        publisher_id = args['id_']
        page = args['page']
        sort_type = args['sort_type']
        page_len = current_app.config['GRAPH_PAGE_LENGTH']

        publisher = models.Publisher.objects(pk=publisher_id).first()

        subscription = models.Subscription.objects(
            openid=current_user.openid,
            type_='publisher',
            target_id=publisher_id,
            status__ne=0 # deal with empty status
        ).first()
        publisher.is_subscribed = subscription is not None
        publisher.subscription_id = subscription.pk if subscription else ''
        if sort_type == 'published':
            graphs = models.Graph.objects(
                owner_id=publisher_id
            ).order_by('-date')[page * page_len: (page + 1) * page_len]
        else:
            graphs = models.Graph.objects(
                owner_id=publisher_id
            ).order_by('-created_ts')[page*page_len: (page+1)*page_len]
        for g in graphs:
            if not g.publisher:
                g.publisher = {
                    'id': publisher.pk,
                    'username': publisher.username,
                    'full_name': publisher.fullname
                }
                # cached full_name in graph will not be updated
                # todo: fix it
                g.save()
        logger.debug('Get publisher list ends.')
        return {'publisher': publisher, 'media': graphs}


ins_api.add_resource(Publisher, '/publisher', endpoint='publisher')


hashtag_list_fields = {
    'hashtag': fields.Nested(models.Marshal.hashtag),
    'media': fields.List(fields.Nested(models.Marshal.graph)),
    'top_posts': fields.List(fields.Nested(models.Marshal.graph)),
}


class Hashtag(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()
        self.parser.add_argument(
            'name',
            dest='name',
            required=True,
            help='Missing hashtag name'
        )
        self.parser.add_argument(
            'page',
            type=inputs.natural,
            default=0,
        )

    @marshal_with(hashtag_list_fields, envelope="data")
    def get(self):
        self.parser.add_argument(
            'sort_type',
            choices=GRAPH_SORT_TYPE,
            default='created',
        )
        logger.debug('Get hashtag list')
        args = self.parser.parse_args()
        hashtag_name = args['name']
        page = args['page']
        sort_type = args['sort_type']
        page_len = current_app.config['GRAPH_PAGE_LENGTH']

        hashtag = models.Hashtag.objects(name=hashtag_name).first()
        if hashtag is None:
            abort(400, 'Specified hashtag not found')

        if sort_type == 'published':
            graphs = models.Graph.objects(
                hashtags=hashtag_name
            ).order_by('-date')[page * page_len: (page + 1) * page_len]
        else:
            graphs = models.Graph.objects(
                hashtags=hashtag_name
            ).order_by('-created_ts')[page*page_len: (page+1)*page_len]

        for g in graphs:
            if not g.publisher:
                try:
                    publisher = models.Publisher.objects.get(pk=g.publisher_id)
                except (mongo_errors.DoesNotExist, mongo_errors.MultipleObjectsReturned):
                    continue
                g.publisher = {
                    'id': publisher.pk,
                    'username': publisher.username,
                    'full_name': publisher.fullname
                }
                # cached full_name in graph will not be updated
                # todo: fix it
                g.save()
        top_posts = []
        for p in hashtag.top_posts:
            g = models.Graph.objects(pk=p['code']).first()
            if g is None:
                continue
            if not g.publisher:
                try:
                    publisher = models.Publisher.objects.get(pk=g.publisher_id)
                except (mongo_errors.DoesNotExist, mongo_errors.MultipleObjectsReturned):
                    continue
                g.publisher = {
                    'id': publisher.pk,
                    'username': publisher.username,
                    'full_name': publisher.fullname
                }
                # cached full_name in graph will not be updated
                # todo: fix it
                g.save()
            top_posts.append(g)

        logger.debug('Get hashtag list ends.')
        return {'hashtag': hashtag, 'top_posts': top_posts, 'media': graphs}


ins_api.add_resource(Hashtag, '/hashtag', endpoint='hashtag')


available_list_fields = {
    'publishers': fields.List(fields.Nested(models.Marshal.publisher)),
    'hashtags': fields.List(fields.Nested(models.Marshal.hashtag)),
}


class Available(Resource):
    @marshal_with(available_list_fields, envelope='data')
    def get(self):
        logger.debug('Get available list')
        publishers = models.Publisher.objects(status__ne=-1)
        hashtags = models.Hashtag.objects(status__ne=-1)

        logger.debug('Get available list ends.')
        return {'publishers': publishers, 'hashtags': hashtags}


ins_api.add_resource(Available, '/available', endpoint='available')


class Search(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()
        self.parser.add_argument(
            'qs',
            required=True,
            help='Missing query string for searching'
        )

    def get(self):
        args = self.parser.parse_args()
        qs = args['qs']
        if '444' in qs:
            logger.info('temporary result for 444')
            return jsonify({
                'data': [{
                    'follower': 75404,
                    'byline': '75k',
                    'fullname': 'x.',
                    'id_': '6297455117',
                    'is_v': True,
                    "position": 1,
                    "type": "publisher",
                    "username": '__4444x__'
                }],
                'qs': qs
            })
        logger.debug('%s is searching for %s', current_user.openid, qs)
        try:
            res_raw = requests.get(INS_UTILS_SEARCH_URL, params={'qs': qs})
            res = res_raw.json()
        except ValueError:
            logger.error(
                'Parsing data returned from hk error: %s',
                res_raw.text
            )
            abort(500, 'Failed to parse search result')
        except requests.exceptions.ConnectTimeout:
            logger.error('Connect to hk timeout %s', traceback.format_exc())
            abort(404, 'Connection timeout')
        except requests.exceptions.ConnectionError:
            logger.error('Connect to hk error: %s', traceback.format_exc())
            abort(404, 'Connection error')

        if res.get('message') is not None:
            logger.error('Searching Failed: %s', res['message'])
            abort(400, str(res['message']))

        if not res.get('data'):
            logger.error('No searching result returned for hk')

        return jsonify({'data': res.get('data'), 'qs': res.get('qs')})


ins_api.add_resource(Search, '/s', endpoint='search')


class Subscribe(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()

    @marshal_with(models.Marshal.subscription, envelope='data')
    def get(self):
        logger.debug('Request for listing subscription')
        subscriptions = models.Subscription.objects(openid=current_user.openid, status__ne=0)
        logger.debug('End listing subscription')
        return list(subscriptions)

    def post(self):
        logger.debug('Request for subscribing')
        self.parser.add_argument(
            'type',
            required=True,
            location='get_json',
            choices=SUBSCRIPTION_TYPE,
            help='Missing/Wrong subscription type'
        )
        args = self.parser.parse_args()
        type_ = args['type']

        try:
            target = getattr(
                self,
                'prepare_{}'.format(type_),
                lambda: None
            )()
        except BadRequest:
            raise
        except:
            logger.error(
                'Prepare failed. Request args: %s Error: %s',
                request.get_json(),
                traceback.format_exc()
            )
            abort(500, 'Creating {} failed'.format(type_))
        try:
            current_user.subscribe(type_, target)
        except:
            logger.error('Creating Subscription failed. Error: %s', traceback.format_exc())
            abort(500, 'Creating Subscription failed')
        try:
            target.enable()
        except:
            logger.error(
                'Enabling target failed. %s Error: %s',
                target.id_,
                traceback.format_exc()
            )
        if type_ == 'publisher':
            logger.info(
                'Create subscription successfully. %s -> %s(%s)',
                current_user.openid,
                target.username,
                type_
            )
            return jsonify({'data': target.id_})
        if type_ == 'hashtag':
            logger.info(
                'Create subscription successfully. %s -> %s(%s)',
                current_user.openid,
                target.name,
                type_
            )
            return jsonify({'data': target.name})

    def delete(self):
        logger.debug('Request for un-subscribing')
        self.parser.add_argument(
            'id',
            required=True,
            location='get_json',
            help='Missing subscription id'
        )
        args = self.parser.parse_args()
        s_id = args['id']

        try:
            subscription = current_user.unsubscribe(s_id)
        except:
            logger.error('Un-subscription failed. Error: %s', traceback.format_exc())
            abort(500, 'Un-subscription failed')
        if subscription is None:
            logger.info('Specified subscription not existed: %s', s_id)
            abort(400, 'Specified subscription not existed')
        logger.info('End for un-subscribing: %s', s_id)
        return jsonify({'data': s_id})

    def prepare_publisher(self):
        self.parser.add_argument(
            'id',
            location='get_json',
            dest='id_',
            required=True,
            help='Missing id'
        )
        self.parser.add_argument(
            'username',
            location='get_json',
            required=True,
            help='Missing username'
        )
        self.parser.add_argument(
            'fullname',
            location='get_json',
            required=True,
            help='Missing fullname'
        )
        args = self.parser.parse_args()
        args.pop('type')
        args['source'] = 'user.searched'

        return models.Publisher.get_instance(**args)

    def prepare_hashtag(self):
        self.parser.add_argument(
            'name',
            location='get_json',
            dest='name',
            required=True,
            help='Missing hashtag name'
        )
        args = self.parser.parse_args()
        args.pop('type')
        args['source'] = 'user.searched'

        return models.Hashtag.get_instance(**args)

    def prepare_place(self):
        logger.info('im place')


ins_api.add_resource(Subscribe, '/subscribe', endpoint='subscribe')


class Like(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()

    @marshal_with(models.Marshal.graph, envelope='data')
    def get(self):
        logger.debug('Request for listing like graphimage %s', current_user.openid)
        self.parser.add_argument(
            'page',
            type=inputs.natural,
            default=0,
        )
        self.parser.add_argument(
            'sort_type',
            choices=GRAPH_SORT_TYPE,
            default='created',
        )
        args = self.parser.parse_args()
        page = args['page']
        sort_type = args['sort_type']
        page_len = current_app.config['GRAPH_PAGE_LENGTH']

        if sort_type == 'published':
            liked_graph = models.Graph.objects(
                liked_by=current_user.openid
            ).order_by('-date')[page * page_len: (page + 1) * page_len]
        else:
            liked_graph = models.Graph.objects(
                liked_by=current_user.openid
            ).order_by('-created')[page*page_len: (page+1)*page_len]

        for g in liked_graph:
            if not g.publisher:
                try:
                    publisher = models.Publisher.objects.get(pk=g.publisher_id)
                except (mongo_errors.DoesNotExist, mongo_errors.MultipleObjectsReturned):
                    continue
                g.publisher = {
                    'id': publisher.pk,
                    'username': publisher.username,
                    'full_name': publisher.fullname
                }
                # cached full_name in graph will not be updated
                # todo: fix it
                g.save()
        logger.debug('Ending listing like graphimage %s', current_user.openid)
        return list(liked_graph)

    def post(self):
        self.parser.add_argument(
            'id',
            required=True,
            location='get_json',
            help='Missing graphimage id'
        )
        args = self.parser.parse_args()
        graph_id = args['id']
        logger.debug('Request for adding like graphimage %s', graph_id)
        try:
            graph = current_user.liked(graph_id)
        except:
            logger.error('Adding like to graphimage failed. Error: %s', traceback.format_exc())
            abort(500, 'Adding like to graphimage failed {}'.format(graph_id))
        if graph is None:
            logger.info('Specified graph not existed: %s', graph_id)
            abort(400, 'Specified graph not existed')
        logger.info(
            'Adding like graphimage successfully: %s -> %s',
            current_user.pk,
            graph_id,
        )
        return jsonify({'data': graph_id})

    def delete(self):
        self.parser.add_argument(
            'id',
            required=True,
            location='get_json',
            help='Missing graphimage id'
        )
        args = self.parser.parse_args()
        graph_id = args['id']
        logger.debug('Request for deleting like graphimage %s', graph_id)
        try:
            graph = current_user.unliked(graph_id)
        except:
            logger.error('Deleting like to graphimage failed. Error: %s', traceback.format_exc())
            abort(500, 'Deleting like to graphimage failed {}'.format(graph_id))
        if graph is None:
            logger.info('Specified graph not existed: %s', graph_id)
            abort(400, 'Specified graph not existed')
        logger.info(
            'Deleting like graphimage successfully: %s -> %s',
            current_user.pk,
            graph_id,
        )
        return jsonify({'data': graph_id})


ins_api.add_resource(Like, '/like', endpoint='like')


class Exclude(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()

    @marshal_with(models.Marshal.publisher, envelope='data')
    def get(self):
        logger.debug('Request for listing excluded publishers %s', current_user.openid)
        try:
            excluded_publishers = models.Publisher.objects(id___in=current_user.excluded_publishers)
        except:
            logger.error('Listing excluded publishers failed. Error: %s', traceback.format_exc())
            abort(500, 'Listing excluded publishers failed')
        logger.debug('Ending listing excluded publishers %s', current_user.openid)
        return list(excluded_publishers)

    def post(self):
        self.parser.add_argument(
            'publisher_id',
            location='get_json',
            dest='publisher_id',
            required=True,
            help='Missing publisher id'
        )
        args = self.parser.parse_args()
        publisher_id = args['publisher_id']
        logger.debug('Request for excluding publisher %s', publisher_id)

        try:
            publisher = current_user.exclude(publisher_id)
        except:
            logger.error('Adding excluding publisher failed. Error: %s', traceback.format_exc())
            abort(500, 'Adding excluding publisher failed {}'.format(publisher_id))
        if publisher is None:
            logger.info('Specified publisher not existed: %s', publisher_id)
            abort(400, 'Specified publisher not existed')
        logger.info(
            'Adding excluding publisher successfully: %s -> %s',
            current_user.pk,
            publisher_id,
        )
        return jsonify({'data': publisher_id})

    def delete(self):
        self.parser.add_argument(
            'publisher_id',
            required=True,
            location='get_json',
            help='Missing publisher id'
        )
        args = self.parser.parse_args()
        publisher_id = args['publisher_id']
        logger.debug(
            'Request for including publisher again %s -> %s',
            current_user.openid,
            publisher_id
        )
        try:
            publisher = current_user.include(publisher_id)
        except:
            logger.error('Including publisher again failed. Error: %s', traceback.format_exc())
            abort(500, 'Including publisher again failed {}'.format(publisher_id))
        if publisher is None:
            logger.info('Specified publisher not existed: %s', publisher_id)
            abort(400, 'Specified publisher not existed')
        logger.info(
            'Including publisher again successfully: %s -> %s',
            current_user.openid,
            publisher_id,
        )
        return jsonify({'data': publisher_id})


ins_api.add_resource(Exclude, '/exclude', endpoint='exclude')


class Profile(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()

    def post(self):
        self.parser.add_argument(
            'nickName',
            location='get_json',
            dest='nickname',
        )
        self.parser.add_argument(
            'avatarUrl',
            location='get_json',
            dest='avatar_url',
        )
        self.parser.add_argument(
            'gender',
            location='get_json',
            dest='gender',
        )
        self.parser.add_argument(
            'city',
            location='get_json',
            dest='city',
        )
        self.parser.add_argument(
            'province',
            location='get_json',
            dest='province',
        )
        self.parser.add_argument(
            'country',
            location='get_json',
            dest='country',
        )
        self.parser.add_argument(
            'language',
            location='get_json',
            dest='language',
        )
        args = self.parser.parse_args()
        user = current_user._get_current_object()
        try:
            user_info = {k:v for k, v in args.items() if v and getattr(user, k, None) != v}
        except:
            logger.error(
                'Checking user profile failed when updating. %s Error: %s',
                current_user.openid,
                traceback.format_exc()
            )
            abort(500, 'Update user profile failed {}'.format(current_user.openid))
        if not user_info:
            logger.debug(
                'User profile is not changed, exit updating: %s',
                current_user.openid,
            )
            return jsonify({'data': current_user.openid})

        logger.debug(
            'Ready for updating user profile: %s -> %s',
            current_user.openid,
            user_info
        )
        try:
            current_user.update_profile(**user_info)
        except:
            logger.error(
                'Update user profile failed. %s Error: %s',
                current_user.openid,
                traceback.format_exc()
            )
            abort(500, 'Update user profile failed {}'.format(current_user.openid))
        logger.info(
            'Update user profile successfully: %s',
            current_user.openid,
        )
        return jsonify({'data': current_user.openid})


ins_api.add_resource(Profile, '/profile', endpoint='profile')


class Login(Resource):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parser = reqparse.RequestParser()
        self.parser.add_argument(
            'code',
            type=str,
            required=True,
            location='get_json',
        )

    def post(self):
        args = self.parser.parse_args()
        code = args['code']
        try:
            token, u_openid = current_app.auth.login(code)
        except:
            logger.error('Login Failed. Error: %s', traceback.format_exc())
            abort(400, 'Login Failed')
        logger.info('User login successfully. %s', u_openid)
        return jsonify({'data': token})


ins_api.add_resource(Login, '/login', endpoint='login')
